/*
 * Copyright Radosław Smogura, 2012. All rights reserved.
 *
 * The content of this file is confidential and it is intelectual property
 * of copyright owners.
 * 
 *
 * $URL: http://reprap-ide.googlecode.com/svn/trunk/RepRapIDE/RepRapProject/src/main/java/eu/softper/reprap/generator/SlicerImpl.java $
 * $Id: SlicerImpl.java 3 2012-11-19 10:58:30Z mail@smogura.eu $
 * $Revision: 3 $
 */
package eu.softper.reprap.generator;

import eu.softper.reprap.utils.HashMapCustomized;
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.util.*;

/**
 *
 * @author Radosław Smogura
 */
public class SlicerImpl<T extends Number & Comparable<T>> {
    public static final MathContext mCtx = new MathContext(1000, RoundingMode.FLOOR);
    public static final int SCALE = 10;
    
    private List<SceneTriangle<T>> orderedTriangles;
    private Iterator<SceneTriangle<T>> orderedIterator;
    private LinkedList<SceneTriangle<T>> processTraingles = new LinkedList<SceneTriangle<T>>();
    private T zVal;
    private BigDecimal stepSize;
    
    int layerNo;
    int layerNoStep;
    
    private SceneTriangle<T> queuedTriangle;

    private GenerationContextImpl ctx;
    private BaseCache<DecimalVertex<T>> verticesCache;
    
    private NumericFactory<T> numericFactory;
    
    /** Holds generated outlines. */
    private HashMapCustomized<SceneLine<T>, SceneLine<T>> outlinesMap;
    
    private Layer layer;

    private void addOutline(SceneLine<T> l, T normal) {
        SceneLine sl = outlinesMap.get(l);
        if (sl != null) {
            if (normal != null)
                sl.getXyNormalsAngles().add(normal);
        }else {
            if (normal != null)
                l.getXyNormalsAngles().add(normal);
            outlinesMap.put(l, l);
        }
    }
    
    public SlicerImpl(NumericFactory<T> numericFactory, GenerationContextImpl ctx) {
        this.ctx = ctx;
        this.verticesCache = ctx.getGeneratorCache().getVerticesCache();
        this.numericFactory = numericFactory;
    }
    
    /**
     * 
     * @param start
     * @param stepSize
     * @param orderedTriangles list of triangles asc ordered on triangle's first
     *        vertex z-position. Much more first traingle vertex z-position must
     *        be minimu position amongother vertices (rughly lowest or equal).
     *        In other words list is ordered basing on triangles' "bottoms".
     */
    public void initialize(int numberStart, int numberInc, BigDecimal start, BigDecimal stepSize, List<SceneTriangle<T>> orderedTriangles) {
        this.zVal = numericFactory.valueOf(start);
        this.stepSize = stepSize;
        this.orderedTriangles = orderedTriangles;
        this.layerNo = numberStart;
        this.layerNoStep = numberInc;
        
        if (stepSize.compareTo(BigDecimal.ZERO) <= 0) {
            throw new IllegalArgumentException("Step size must be positive");
        }
        /*
         * Do more initialization, prepare iterator.
         */
        this.orderedIterator = orderedTriangles.iterator();
        if (!orderedIterator.hasNext()) {
            return;
        }

        queuedTriangle = orderedIterator.next();
    }



    /**
     * Projects triangle onto surface at height zVal. The 1st traingle vertex is
     * at surface height or below. For each vertex compare two side vertices.
     */
    protected void projectOutline() {
        Iterator<SceneTriangle<T>> i = processTraingles.iterator();
        while (i.hasNext()) {
            SceneTriangle t = i.next();
            DecimalVertex<T>[] verts = t.getVertices();
            
            /*
             * Calulate normal angle on X-Y plane
             */
            DecimalVertex<T> normal = t.getNormal();
            final T normalAngle;
            if (numericFactory.isNotZero(normal.getX()) || numericFactory.isNotZero(normal.getY()))
            {
                double x = normal.getX().doubleValue();
                double y = normal.getY().doubleValue();
                //Special treatment for x == 0
                double angle = Math.atan2(y, x);
                double angleDegree = angle * 180.0d / Math.PI;
                //Meybe better to store angle in radians
                normalAngle = numericFactory.valueOf(angleDegree);
            }else {
                normalAngle = null;
            }
            
            for (int v = 0; v < 3; v++) {
                DecimalVertex<T> v1 = verts[(v + 1) % 3];
                DecimalVertex<T> v2 = verts[v];
                DecimalVertex<T> v3 = verts[(v + 2) % 3];

                /*
                 * If at leas one line of vertes is parallel to surface put
                 * whole line and check if to put 2nd one
                 */
                final int cmp1Z = v1.getZ().compareTo(zVal);
                final int cmp2Z = v2.getZ().compareTo(zVal);
                final int cmp3Z = v3.getZ().compareTo(zVal);

                boolean l12Parallel = cmp1Z == 0 && cmp2Z == 0;
                boolean l13Parallel = cmp1Z == 0 && cmp3Z == 0;
                boolean l23Parallel = cmp2Z == 0 && cmp3Z == 0;
                if (l12Parallel || l13Parallel || l23Parallel) {
                    /*
                     * We add only 1st line, 2nd line will or was added if
                     * needed for (v+1) mod 3. There is no need to process
                     * anything else because if two verts lies "inside"
                     */
                    if (l12Parallel) {
                        addOutline(new SceneLine(verticesCache.intern(v1), verticesCache.intern(v2)), normalAngle);
                    }
                    /*
                     * Nothing more to process for this vertex, but we need to
                     * continue triangle processing because, we may need to add
                     * more outlines.
                     */
                    continue;
                } else {
                    /*
                     * We know that at least one vertex is equal or below
                     * surface.
                     */
                    if (cmp1Z == 0 || cmp2Z == 0 || cmp3Z == 0) {
                        /*
                         * One point is on surface, but calulate, corner case -
                         * calculate only if 1st one. Only one may fill this
                         * criterio, oherwaise line will be parrallel.
                         */
                        if (cmp1Z == 0 && (cmp2Z + cmp3Z == 0)) {
                            /*
                             * Calculate line v1 -> solve(v2,v3).
                             */
                            addOutline(new SceneLine(
                                        verticesCache.intern(v1),
                                        verticesCache.intern(v1.intersectWithPlane(v2, v3, zVal))),
                                    normalAngle);
                            /*
                             * Nothing more to process.
                             */
                            break;
                        }
                    } else {
                        /*
                         * Take only this point which is above, and other below.
                         */
                        if (cmp1Z > 0 && cmp2Z < 0 && cmp3Z < 0
                                || cmp1Z < 0 && cmp2Z > 0 && cmp3Z > 0) {
                            /*
                             * Calculate line solve(v1, v2) -> solve (v1,v3)
                             */
                            addOutline(new SceneLine<T>(
                                        verticesCache.intern(v1.intersectWithPlane(v1, v2, zVal)),
                                        verticesCache.intern(v1.intersectWithPlane(v1, v3, zVal))),
                                    normalAngle);
                            /*
                             * Nothing more to process.
                             */
                            break;
                        }
                    }
                }
            }
        }
    }

    protected void addNextTriangles() {
        while (queuedTriangle != null) {
            if (queuedTriangle.getV1().getZ().compareTo(zVal) <= 0) {
                processTraingles.add(queuedTriangle);
                /*
                 * Read next triangle.
                 */
                if (orderedIterator.hasNext()) {
                    queuedTriangle = orderedIterator.next();
                } else {
                    queuedTriangle = null;
                }
            } else {
                // Triangle is above,
                break;
            }
        }
    }

    /** This removes upper and bottom triangles, which has to small height
     * to be counted.
     */
    protected void removeUnusedTraingles() {
        Iterator<SceneTriangle<T>> i = processTraingles.iterator();
        while (i.hasNext()) {
            SceneTriangle<T> t = i.next();
            /*
             * t.getV1().getZ().compareTo(zVal) < 0 is uneeded check due to
             * increasing z step. -It's not true for add we have less or equal
             * and remove may be in same step
             */
            if (t.getV1().getZ().compareTo(zVal) < 0
                    && t.getV2().getZ().compareTo(zVal) < 0
                    && t.getV3().getZ().compareTo(zVal) < 0) {
                i.remove();
            }
        }
    }

    public Layer getLayer() {
        return layer;
    }
    
    /**
     *
     * @return {@code false} if nor more layers exists, {@code true} otherwise
     */
    public boolean processLayer() {
        /* If no queued triangle, when initializing at leas one traingle will
         * be set, until there is no triangles, but even if there is no
         * more triangles some of them may still be on the list.
         */
        if (queuedTriangle == null && processTraingles.isEmpty()) {
            return false;
        }
        /*
         * Add next elements, do it first small triangles with height less then
         * Z step size will be removed in next step. First vertex of triangle
         * has lowest Z from other vertices.
         */
        addNextTriangles();

        /*
         * Remove triangles that will not be processed.
         */
        removeUnusedTraingles();

        /*
         * And do layer processing.
         */
        outlinesMap = new HashMapCustomized<SceneLine<T>, SceneLine<T>>(new SceneLineHashProvider<T>());
        layer = null;
        projectOutline();

        Set<SceneLine> outlines = new HashSet<SceneLine>(outlinesMap.values());
        outlinesMap.clear();
        outlinesMap = null;
        
        layer = new Layer(layerNo, BigDecimal.valueOf(zVal.doubleValue()), outlines);
        
        new TriangleFanRemover().removeInnerLines(layer);
        
        new AngleReducer<T>(numericFactory, layer).reduceAngles();
        
        new OutlineGenerator(numericFactory, layer, null).generateOutline();
        
        InfillGeneratorConfig infillCfg = new InfillGeneratorConfig(
                (layerNo % 2) == 0 ? ctx.getEvenInfill() : ctx.getOddInfill(), 
                ctx.getInfillRadius());
        infillCfg.setStep(ctx.getInfillStep());
        new InfillGenerator(numericFactory, layer, infillCfg).generateInfill();
        
        /*
         * Prepare next postion.
         */
        zVal = numericFactory.add(zVal, numericFactory.valueOf(stepSize));
        layerNo += layerNoStep;
        
        return true;
    }
    
    private static class SceneLineHashProvider<T extends Number & Comparable<T>> implements HashMapCustomized.EqualityProvider<SceneLine<T>> {
        @Override
        public int hashCode(SceneLine<T> object) {
            return  object.getV1().getX().intValue() +
                    object.getV1().getY().intValue() * 2048 +
                    object.getV2().getX().intValue() +
                    object.getV2().getY().intValue() * 2048;
        }

        @Override
        public boolean equals(SceneLine<T> a, SceneLine<T> b) {
            return (a.getMaxX().compareTo(b.getMaxX()) == 0) &&
                    (a.getMinX().compareTo(b.getMinX()) == 0) &&
                    (a.getMaxY().compareTo(b.getMaxY()) == 0) &&
                    (a.getMinY().compareTo(b.getMinY()) == 0);
                    
        }
    }
}
